﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using System.ComponentModel;
using System.Text;

namespace SmartFront.Toolkit.Core
{
    /// <summary>
    /// Collection related Extension Methods
    /// </summary>
    public static class CollectionExtensionMethods
    {
        /// <summary>
        /// Fors the each.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="coll">The coll.</param>
        /// <param name="predicate">The predicate.</param>
        public static void ForEach<T>(this Collection<T> coll, Action<T> predicate)
        {
            foreach (var item in coll)
            {
                predicate.Invoke(item);
            }
        }

		/// <summary>
		/// Inserts the range.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="collection">The collection.</param>
		/// <param name="insertIndex">Index of the insert.</param>
		/// <param name="range">The range.</param>
		public static void InsertRange<T>(this Collection<T> collection, int insertIndex, IEnumerable<T> range)
		{
			foreach (T item in range)
			{
				collection.Insert(insertIndex, item);
                insertIndex++;
			}
		}

    	/// <summary>
        /// Clears the and add range.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="col">The col.</param>
        /// <param name="range">The range.</param>
        public static void ClearAndAddRange<T>(this Collection<T> col, IEnumerable<T> range)
        {
            col.Clear();
            if (null != range)
            {
                foreach (var item in range)
                {
                    col.Add(item);
                }
            }
        }

		/// <summary>
		/// Clears the and add range.
		/// </summary>
		/// <typeparam name="TKey">The type of the key.</typeparam>
		/// <typeparam name="TValue">The type of the value.</typeparam>
		/// <param name="dic">The dic.</param>
		/// <param name="range">The range.</param>
		public static void ClearAndAddRange<TKey, TValue>(this IDictionary<TKey, TValue> dic, IEnumerable<KeyValuePair<TKey, TValue>> range)
		{
            dic.Clear();
            if (null != range)
            {
                foreach (var item in range)
                {
                    dic.Add(item.Key, item.Value);
                }
            }
		}

        /// <summary>
        /// Adds the range of items from <paramref name="range"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="col">The col.</param>
        /// <param name="range">The range.</param>
        public static void AddRange<T>(this Collection<T> col, IEnumerable<T> range)
        {
            foreach (var item in range)
            {
                col.Add(item);
            }
        }

		/// <summary>
		/// Adds the range.
		/// </summary>
		/// <typeparam name="TKey">The type of the key.</typeparam>
		/// <typeparam name="TValue">The type of the value.</typeparam>
		/// <param name="dic">The dic.</param>
		/// <param name="range">The range.</param>
		public static void AddRange<TKey, TValue>(this IDictionary<TKey, TValue> dic, IEnumerable<KeyValuePair<TKey, TValue>> range)
		{
            foreach (var item in range)
            {
                dic.Add(item.Key, item.Value);
            }
		}

        /// <summary>
        /// Converts the enumerable item to a <see cref="ReadOnlyCollection{T}"/>
        /// </summary>
        /// <typeparam name="T">the type of the item in the collection</typeparam>
        /// <param name="enumerable">The enumerable.</param>
        /// <returns></returns>
        public static ReadOnlyCollection<T> AsReadOnly<T>(this IEnumerable<T> enumerable)
        {
            return new ReadOnlyCollection<T>(enumerable.ToList<T>());
        }

        /// <summary>
        /// Converts the enumerable item to a <see cref="Collection{T}"/>
        /// </summary>
        /// <typeparam name="T">the type of the item in the collection</typeparam>
        /// <param name="enumerable">The enumerable.</param>
        /// <returns></returns>
        public static Collection<T> ToCollection<T>(this IEnumerable<T> enumerable)
        {
            return new Collection<T>(enumerable.ToList<T>());
        }

        /// <summary>
        /// Toes the collection.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static Collection<T> ToSingleItemCollection<T>(this T obj)
        {
            return new Collection<T>
            {
                obj
            };
        }

		/// <summary>
        /// Removes all.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="thisObject">The this object.</param>
        /// <param name="predicate">The predicate.</param>
        public static Collection<T> RemoveAll<T>(this IList<T> thisObject, Predicate<T> predicate)
        {
            Collection<T> deletedColletion = new Collection<T>();
            for (int i = 0; i < thisObject.Count; i++)
            {
                T item = thisObject[i];
                if (predicate(item))
                {
                    thisObject.RemoveAt(i);
                    i--; //we just removed an item so make sure we don't skip one.
                    deletedColletion.Add(item);
                }
            }
            return deletedColletion;
        }

        /// <summary>
        /// Tries the get value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dictionary">The dictionary.</param>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static bool TryGetValue<T>(this IDictionary<string, T> dictionary, string key, out T value)
        {
            var result = false;
            value = default(T);

            if (dictionary.ContainsKey(key))
            {
				if (dictionary[key] is T)
				{
					value = (T)dictionary[key];
					result = true;
				}
				else if (dictionary[key] == null)
				{
					result = true;
				}
            }

            return result;
        }

        /// <summary>
        /// Tries the get value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dictionary">The dictionary.</param>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static bool TryGetValue<T>(this IDictionary<string, object> dictionary, string key, out T value)
        {
            var result = false;
            value = default(T);

            if (dictionary.ContainsKey(key))
            {
                if (dictionary[key] is T)
                {
                    value = (T)dictionary[key];
                    result = true;
                }
                else if (dictionary[key] == null)
                {
                    result = true;
                }
            }

            return result;
        }

        /// <summary>
        /// Traverses the specified source.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">The source.</param>
        /// <param name="fnRecurse">The fn recurse.</param>
        /// <returns></returns>
        public static IEnumerable<T> Traverse<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> fnRecurse)
        {
            foreach (T item in source)
            {
                yield return item;
                IEnumerable<T> seqRecurse = fnRecurse(item);
                if (seqRecurse != null)
                {
                    foreach (T itemRecurse in Traverse(seqRecurse, fnRecurse))
                    {
                        yield return itemRecurse;
                    }
                }
            }
        }

        /// <summary>
        /// Traverses to level.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">The source.</param>
        /// <param name="fnRecurse">The fn recurse.</param>
        /// <param name="finalLevel">The final level.</param>
        /// <param name="currentLevel">The current level.</param>
        /// <returns></returns>
        public static IEnumerable<T> TraverseToLevel<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> fnRecurse, int finalLevel, int currentLevel)
        {
            int nextLeve = currentLevel + 1;

            foreach (T item in source)
            {
                IEnumerable<T> seqRecurse = fnRecurse(item);
                if (seqRecurse != null)
                {
                    if (currentLevel == finalLevel)
                    {
                        yield return item;
                        continue;
                    }
                    else if (currentLevel < finalLevel || finalLevel == -1)
                    {
                        foreach (T itemRecurse in TraverseToLevel(seqRecurse, fnRecurse, finalLevel, nextLeve))
                        {
                            yield return itemRecurse;
                        }
                    }
                }
                else
                {
                    yield return item;
                }
            }
        }

		/// <summary>
		/// Adds the range.
		/// </summary>
		/// <typeparam name="TKey">The type of the key.</typeparam>
		/// <typeparam name="TValue">The type of the value.</typeparam>
		/// <param name="dictionary">The dictionary.</param>
		/// <param name="range">The range.</param>
		public static void AddRange<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, IDictionary<TKey, TValue> range)
		{
			foreach (var item in range)
			{
				dictionary.Add(item.Key, item.Value);
			}
		}

		/// <summary>
		/// Clears the and add range.
		/// </summary>
		/// <typeparam name="TKey">The type of the key.</typeparam>
		/// <typeparam name="TValue">The type of the value.</typeparam>
		/// <param name="dictionary">The dictionary.</param>
		/// <param name="range">The range.</param>
		public static void ClearAndAddRange<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, IDictionary<TKey, TValue> range)
		{
			dictionary.Clear();

			foreach (var item in range)
			{
				dictionary.Add(item.Key, item.Value);
			}
		}

        /// <summary>
        /// Sorts the specified source.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">The source.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="sortExpression">The sort expression.</param>
        /// <example>
        /// Sort Expression contains name of property and order
        /// For Order by Descending order: EventDataCollection.Sort("Date desc");
        /// For order by Acending Order: EventDataCollection.Sort("Date");
        /// </example>
        public static void Sort<T>(this Collection<T> source, string propertyName, ListSortDirection sortExpression)
        {
            var tmpColl = new Collection<T>();
            ParameterExpression param = Expression.Parameter(typeof(T), string.Empty);
            MemberExpression property = Expression.Property(param, propertyName);
            Expression<Func<T, object>> sortLambda =
                Expression.Lambda<Func<T, object>>(Expression.Convert(property, typeof(object)), param);
            if (sortExpression == ListSortDirection.Descending)
            {
                tmpColl.AddRange(source.AsQueryable().OrderByDescending(sortLambda));
            }
            else
            {
                tmpColl.AddRange(source.AsQueryable().OrderBy(sortLambda));
            }

            source.Clear();
            source.AddRange(tmpColl);
        }

        /// <summary>
        /// Sorts the specified source.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">The source.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="sortExpression">The sort expression.</param>
        /// <param name="comparer">The comparer.</param>
        public static void Sort<T>(this Collection<T> source, string propertyName, ListSortDirection sortExpression, IComparer<object> comparer)
        {
            var tmpColl = new Collection<T>();
            ParameterExpression param = Expression.Parameter(typeof(T), string.Empty);
            MemberExpression property = Expression.Property(param, propertyName);
            Expression<Func<T, object>> sortLambda =
                Expression.Lambda<Func<T, object>>(Expression.Convert(property, typeof(object)), param);
            if (sortExpression == ListSortDirection.Descending)
            {
                tmpColl.AddRange(source.AsQueryable().OrderByDescending(sortLambda, comparer));
            }
            else
            {
                tmpColl.AddRange(source.AsQueryable().OrderBy(sortLambda, comparer));
            }

            source.Clear();
            source.AddRange(tmpColl);
        }

        /// <summary>
        /// Sorts the return.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">The source.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="sortExpression">The sort expression.</param>
        /// <returns></returns>
        public static Collection<T> SortReturn<T>(this Collection<T> source, string propertyName, ListSortDirection sortExpression)
        {
            var tmpColl = new Collection<T>();
            ParameterExpression param = Expression.Parameter(typeof(T), string.Empty);
            MemberExpression property = Expression.Property(param, propertyName);
            Expression<Func<T, object>> sortLambda =
                Expression.Lambda<Func<T, object>>(Expression.Convert(property, typeof(object)), param);
            if (sortExpression == ListSortDirection.Descending)
            {
                tmpColl.AddRange(source.AsQueryable().OrderByDescending(sortLambda));
            }
            else
            {
                tmpColl.AddRange(source.AsQueryable().OrderBy(sortLambda));
            }

            return tmpColl;
        }

        /// <summary>
        /// Sorts the return.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">The source.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="sortExpression">The sort expression.</param>
        /// <param name="comparer">The comparer.</param>
        /// <returns></returns>
        public static Collection<T> SortReturn<T>(this Collection<T> source, string propertyName, ListSortDirection sortExpression, IComparer<object> comparer)
        {
            var tmpColl = new Collection<T>();
            ParameterExpression param = Expression.Parameter(typeof(T), string.Empty);
            MemberExpression property = Expression.Property(param, propertyName);
            Expression<Func<T, object>> sortLambda =
                Expression.Lambda<Func<T, object>>(Expression.Convert(property, typeof(object)), param);
            if (sortExpression == ListSortDirection.Descending)
            {
                tmpColl.AddRange(source.AsQueryable().OrderByDescending(sortLambda, comparer));
            }
            else
            {
                tmpColl.AddRange(source.AsQueryable().OrderBy(sortLambda, comparer));
            }

            return tmpColl;
        }

        /// <summary>
        /// Indexes the of.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">The source.</param>
        /// <param name="func">The func.</param>
        /// <returns></returns>
        public static int IndexOf<T>(this IEnumerable<T> source, Predicate<T> func)
        {
            int itemIndex = -1;
            var itemCount = source.Where((item, index) =>
            {
                if (func(item) == true)
                {
                    itemIndex = index;
                    return true;
                }
                return false;
            }
            ).Count();

            return itemIndex;
        }

        /// <summary>
        /// Indexes the item.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">The source.</param>
        /// <param name="func">The func.</param>
        /// <returns></returns>
		public static T IndexItem<T>(this IEnumerable<T> source, Predicate<int> func)
		{
			T result = default(T);
			var itemCount = source.Where((item, index) =>
			{
				if (func(index) == true)
				{
					result = item;
					return true;
				}
				return false;
			}
			).Count();

			return result;
		}

        /// <summary>
        /// Binaries the search.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="itemCollection">The item collection.</param>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        public static T BinarySearch<T>(this Collection<T> itemCollection, T item) where T : IComparable
        {
            T result = default(T);
            int begin = 0;
            int end = itemCollection.Count - 1;
            while (begin <= end)
            {
                int index = (begin + end) / 2;
                T itemAtIndex = itemCollection[index];
                if (item.CompareTo(itemAtIndex) < 0)
                    end = index - 1;
                else if (item.CompareTo(itemAtIndex) > 0)
                    begin = index + 1;
                else if (item.CompareTo(itemAtIndex) == 0)
                {
                    result = itemAtIndex;
                    break;
                }
            }
            return result;
        }
    }
}
